#include <boost/program_options.hpp>
#include <fstream>
#include <netdb.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <boost/asio.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <exception>       

#include "options.h"
#include "format.h"

server_parameters g_config;

std::string get_part(const std::string &_str, const std::string &_delim, std::string::size_type &_pos)
{
    
    std::string part;
    std::string::size_type pos = _str.find(_delim, _pos);
    
    if (pos != std::string::npos)
    {
	part = _str.substr( _pos, pos - _pos);
	_pos = pos + _delim.length();
    }
    
    return part;
}

void validate (boost::any& v, std::vector<std::string> const& values, uid_value* target_type, int)
{
  using namespace boost::program_options;
  validators::check_first_occurrence (v);
  std::string const& s = validators::get_single_string (values);

  // check for number
  try {
    v = boost::any (uid_value(boost::lexical_cast<uid_t> (s)));
    return;
  }
  catch (std::bad_cast const&) {}

  // resolve by getpwnam
  //
  struct passwd *pwd = ::getpwnam (s.c_str ());
  if (pwd)
  {
    v = boost::any (uid_value(pwd->pw_uid));
    ::endpwent ();
    return;
  }

  ::endpwent ();
  throw validation_error(validation_error::invalid_option_value, "invalid user name");
}

void validate (boost::any& v, std::vector<std::string> const& values, gid_value* target_type, int)
{
  using namespace boost::program_options;
  validators::check_first_occurrence (v);
  std::string const& s = validators::get_single_string (values);

  // check for number
  try {
    v = boost::any (gid_value(boost::lexical_cast<gid_t> (s)));
    return;
  }
  catch (std::bad_cast const&) {}

  // resolve by getpwnam
  //
  struct group *g = ::getgrnam (s.c_str ());
  if (g)
  {
    v = boost::any (gid_value(g->gr_gid));
    ::endgrent ();
    return;
  }

  ::endgrent ();
  throw validation_error(validation_error::invalid_option_value, "invalid group name");
}

void validate (boost::any& v, std::vector<std::string> const& values, server_parameters::remote_point* target_type, int)
{
    boost::program_options::validators::check_first_occurrence (v);
    std::string const& s =  boost::program_options::validators::get_single_string (values);
   
    server_parameters::remote_point rp;
    
    rp.m_port = 0;
  
    std::string::size_type pos = 0;

    std::string buffer = get_part(s, ":", pos);
    
    
    if (!buffer.empty()) 
    {
		rp.m_host_address = buffer;
		
		std::string tmp = s.substr(pos,s.length() - pos);
		rp.m_port = atoi(tmp.c_str());
    }
    else
    {
	throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value, "missing port number");
    }
    
    if (rp.m_port == 0)
    {
    	throw boost::program_options::validation_error(boost::program_options::validation_error::invalid_option_value, "missing port number");
    }	
    
    v = boost::any(rp);
}


#define DEF_CONFIG	"./asudsrv.conf"
#define DEF_PID_FILE	"/var/run/asudserver.pid"

bool server_parameters::parse_config(int _argc, char* _argv[], std::ostream& _out)
{
    try 
    {
        std::string config_file;
    
        boost::program_options::options_description command_line_opt("Command line options");
                
        command_line_opt.add_options()
            ("version,v", "print version string")
            ("help,h", "produce help message")
            ("fg,f", "run at foreground")
            ("pid-file,p", boost::program_options::value<std::string>(&m_pid_file)->default_value(DEF_PID_FILE), "name of pid file.")
            ("config,c", boost::program_options::value<std::string>(&config_file)->default_value(DEF_CONFIG), "name of configuration file.")
            ;
    
        boost::program_options::options_description config_options("Configuration");
        
        config_options.add_options()
    	    ("listen", boost::program_options::value<remote_point>(&m_listen_point), "listen on ip:port")
    	    ("user", boost::program_options::value<uid_value>(&m_uid), "set uid after port bindings")
    	    ("group", boost::program_options::value<gid_value>(&m_gid), "set gid after port bindings")
    	    ("workers", boost::program_options::value<unsigned int>(&m_worker_count)->default_value(5), "workers count")
    	    
    	    ("dump-all-packet", boost::program_options::value<bool>(&m_dump_all_packet)->default_value(false), "dump all packet")
    	    ("dump-all-query", boost::program_options::value<bool>(&m_dump_all_query)->default_value(false), "dump sql query")
    	    
	      	("connect.host", boost::program_options::value<std::string>(&connect_host_), "connect host")
	      	("connect.user", boost::program_options::value<std::string>(&connect_user_), "connect user")
	      	("connect.password", boost::program_options::value<std::string>(&connect_password_), "connect password")
	      	("connect.database", boost::program_options::value<std::string>(&connect_database_), "connect database")

	      	("time.rescan", boost::program_options::value<unsigned int>(&m_rescan_time)->default_value(5), "controller rescan time in sec")
	      	("time.sync-diff-sec", boost::program_options::value<int>(&time_sync_diff_sec_)->default_value(5), "sync if time greate differ")

			  ;

        boost::program_options::variables_map vm;
        boost::program_options::positional_options_description p;
                
        boost::program_options::store(boost::program_options::command_line_parser(_argc, _argv).options(command_line_opt).run(), vm);
        
        notify(vm);
        
        
        m_foreground = (vm.count("fg") != 0);
        
        if (vm.count("help")) 
        {
            _out << command_line_opt << std::endl;
            return false;
        }

        if (vm.count("version")) 
        {
            _out << "0.0.1" << std::endl;
            return false;
        }
        
        std::ifstream ifs(config_file.c_str());
        
        if (!ifs)
        {
	    _out << "Can not open config file: " << config_file << std::endl;
            return false;
        }
        else
        {
            store(parse_config_file(ifs, config_options, true), vm);
            notify(vm);
        }

        return true;
    }
    catch(const std::exception& e)
    {
        _out << "Config file error:" << e.what() << std::endl;
        return false;
    }
}    

